use super::{
    box_psn, err, err_value, json, load_ring, ok, throw_err, try_rm_task, Base, CBase, Client,
    ECode, Lock, MapSv, RNull, Resp, TBase, VBase, VMap, Value,
};
use base::tbase::log_stock;
use std::collections::HashMap;
use tracing::{instrument, warn};

pub struct Away;

impl Base for Away {}
impl TBase for Away {}

impl Away {
    pub async fn do_cmd(&self, para: &Value, cmd: &str, client: &Client, lock: Lock) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        match cmd {
            "boxpsn" => self.do_boxpsn(line, data, client).await,
            "count" => self.do_count(line, client).await,
            "move" => self.do_move(line, data, client, lock).await,
            "outbox" => self.do_outbox(line, data, client).await,
            "pure" => self.do_pure(line, data, client, lock).await,
            "qbc" | "db" | "dt" => self.calc_data(cmd, line, data, client).await,
            "load" => self.do_load(line, data, client).await,
            "scrap" => self.do_scrap(line, data, client).await,
            "task" => self.do_task(line, data, client, lock).await,
            _ => throw_err!(ECode::NoServe, "no away serve"),
        }
    }

    #[instrument(skip_all, name = "do_scrap")]
    async fn do_scrap(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let tp = data.ck_str("tp")?;
        let box_sn = data.ck_str("box")?;

        let body = self.do_boxpsn(line, data, client).await?;
        let body = body.ck_array("data")?;
        let tab = format!("scrap{tp}");

        let mut psn_v = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;
            let sn = d.ck_str("sn")?;
            psn_v.push(sn);

            let _ = log_stock(line, &tab, sn, "away", d, None, client, "log").await;
        }

        let cmd = json!({ "line": line, "v": psn_v });
        client.cfgdb(&cmd, &format!("{tp}/rmset")).await?;

        let cmd = json!({ "line": line });
        let _ = client
            .cfgdb(&cmd, &format!("file/rmfile/box_{box_sn}"))
            .await;

        ok!()
    }

    #[instrument(skip_all, name = "do_boxpsn")]
    async fn do_boxpsn(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let box_sn = data.ck_str("box")?;
        let tp = data.ck_str("tp")?;

        let (box_tp, psn_set) = box_psn(line, box_sn, client).await?;

        if box_tp != tp {
            throw_err!(ECode::Para, "box tp error");
        }

        let cmd = json!({ "line": line });
        let body = client.cfgdb(&cmd, &format!("{tp}/list")).await?;
        let body = body.ck_array("data")?;

        let mut ret = Vec::new();

        for d in body.iter() {
            let d = d.ckobj()?;
            let sn = d.ck_str("sn")?;

            if !psn_set.contains(sn) {
                continue;
            }

            ret.push(d);
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_outbox")]
    async fn do_outbox(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let sn = data.ck_str("k")?;
        let box_sn = data.ck_str("box")?;
        let tp = data.ck_str("tp")?;

        let cmd = json!({
            "line": line,
            "v": {
                "sn": sn,
                "box": "",
            }
        });
        let _ = client.cfgdb(&cmd, &format!("{tp}/mditem")).await;

        let cmd = json!({ "line": line, "sn": sn });
        client.cfgdb(&cmd, &format!("file/rm/box_{box_sn}")).await
    }

    #[instrument(skip_all, name = "calc_data")]
    async fn calc_data(&self, tp: &str, line: &str, data: &MapSv, client: &Client) -> Resp {
        let cmd = json!({
            "line": line,
            "k": data.ck_str("tech")?,
        });

        let body = client.cfgdb(&cmd, "away/listpsn").await?;
        let body = body.ck_array("data")?;

        let base = CBase;

        match tp {
            "qbc" => base.qbc(data, &body).await,
            "dt" => base.dt(data, &body).await,
            "db" => base.db(data, &body).await,
            _ => throw_err!(ECode::Para, "tp error"),
        }
    }

    #[instrument(skip_all, name = "do_pure")]
    async fn do_pure(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let tsn = data.ck_str("k")?;
        let psn_v = data.ck_array("v")?;

        let cmd = json!({
            "line": line,
            "rule": "task",
        });

        let body = client.cfgdb(&cmd, "code/gen").await?;

        let task_sn = body.ck_obj("data")?.ck_str("psn")?;
        let task_sn = format!("R{task_sn}");

        warn!("task step: create task");

        let cmd = json!({
            "line": line,
            "v": {
                "sn": task_sn,
                "pstart": base::cur_time("%Y-%m-%d %H:%M:%S"),
                "pend": self.next_cur_time_day(1, "%Y-%m-%d %H:%M:%S")?,
                "pnum": psn_v.len(),
                "state": "open",
                "tech": tsn,
            },
        });

        let _ = client.cfgdb(&cmd, "task/add").await?;

        warn!("task step: add psn");

        let cmd = json!({
            "line": line,
            "k": task_sn,
            "v": psn_v,
        });
        let _ = client.cfgdb(&cmd, "task/movepsn").await?;

        warn!("task step: update psn");

        let mut psn_val = Vec::new();

        for psn in psn_v.iter() {
            psn_val.push(json!({
                "sn": psn,
                "task": task_sn,
                "boxpack": "",
                "boxpair": "",
            }));
        }

        let cmd = json!({
            "line": line,
            "v": psn_val,
        });

        let _ = client.cfgdb(&cmd, "psn/mmd").await?;

        warn!("task step: remove psn from away store");

        let cmd = json!({
            "line": line,
            "rmpsn": false,
            "k": tsn,
            "v": psn_v,
        });

        client.cfgdb(&cmd, "away/rmset").await
    }

    async fn do_count(&self, line: &str, client: &Client) -> Resp {
        let cmd = json!({ "line": line });

        let body = client.cfgdb(&cmd, "away/list").await?;
        let body = body.ck_array("data")?;

        let mut ret = Vec::new();

        for d in body.iter() {
            let sn = d.ck_str("sn")?;

            let cmd = json!({
                "line": line,
                "k": sn,
            });

            if let Ok(body_psn) = client.cfgdb(&cmd, "away/listpsn").await {
                let body_psn = body_psn.ck_array("data")?;
                let num = body_psn.len();

                ret.push(json!({
                    "sn": sn,
                    "num": num,
                }));
            }
        }

        if ret.is_empty() {
            throw_err!(ECode::EmptyData, "no data");
        }

        ok!(ret)
    }

    #[instrument(skip_all, name = "do_task")]
    async fn do_task(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;
        let val = data.ck_obj("v")?;

        let tsn = val.ck_str("tsn")?;
        let batch_sn = val.ck_str("sn")?;
        let cage_sn = val.ck_str("cage")?;
        let pnum = val.ck_i64("num")?;

        warn!("task step: load pair");

        let psn_set = load_ring("re_pair", tsn, line, client, None).await?;

        let mut task_val = HashMap::new();
        let mut psn_v = Vec::new();
        let mut ballch = None;

        warn!("task step: calc psn");

        for (psn, data) in psn_set.iter() {
            let data = data.ckobj()?;

            let batch = match data.k_array("batch") {
                Some(v) => v,
                None => {
                    continue;
                }
            };

            let mut find = false;

            for d in batch.iter() {
                let d = d.ckobj()?;
                let sn = d.ck_str("sn")?;

                if sn == batch_sn {
                    find = true;
                    task_val.insert("ballch", json!(d));
                    ballch = Some(d.clone());
                    break;
                }
            }

            if !find {
                continue;
            }

            psn_v.push(psn.to_string());

            let len = psn_v.len() as i64;
            if len >= pnum {
                break;
            }
        }

        if psn_v.is_empty() {
            throw_err!(ECode::EmptyData, "no psn");
        }

        warn!("task step: gen task sn");

        let cmd = json!({
            "line": line,
            "rule": "task",
        });

        let body = client.cfgdb(&cmd, "code/gen").await?;

        let task_sn = body.ck_obj("data")?.ck_str("psn")?;
        let task_sn = format!("R{task_sn}");

        warn!("task step: create task");

        let pstart = base::cur_time("%Y-%m-%d %H:%M:%S");
        let pend = self.next_cur_time_day(1, "%Y-%m-%d %H:%M:%S")?;

        task_val.insert("sn", json!(task_sn));
        task_val.insert("pstart", json!(pstart));
        task_val.insert("pend", json!(pend));
        task_val.insert("pnum", json!(pnum));
        task_val.insert("state", json!("open"));
        task_val.insert("bsn", json!(batch_sn));
        task_val.insert("tech", json!(tsn));
        task_val.insert("cage", json!(cage_sn));

        let cmd = json!({
            "line": line,
            "v": task_val,
        });

        let _ = client.cfgdb(&cmd, "task/add").await?;

        warn!("task step: add psn");

        let cmd = json!({
            "line": line,
            "k": task_sn,
            "v": psn_v,
        });
        let _ = client.cfgdb(&cmd, "task/movepsn").await?;

        warn!("task step: update psn");

        let mut psn_val = Vec::new();

        for psn in psn_v.iter() {
            let mut cmd = HashMap::new();

            cmd.insert("sn", json!(psn));
            cmd.insert("task", json!(task_sn));
            cmd.insert("cage", json!(cage_sn));
            cmd.insert("boxpack", json!(""));
            cmd.insert("boxpair", json!(""));

            if let Some(ref ball) = ballch {
                for (k, v) in ball.iter() {
                    if k == "sn" {
                        cmd.insert("bsn", json!(v));
                        continue;
                    }

                    cmd.insert(k, json!(v));
                }
            }

            psn_val.push(json!(cmd));
        }

        let cmd = json!({
            "line": line,
            "v": psn_val,
        });

        let _ = client.cfgdb(&cmd, "psn/mmd").await?;

        warn!("task step: remove psn from away store");

        let cmd = json!({
            "line": line,
            "rmpsn": false,
            "k": tsn,
            "v": psn_v,
        });

        client.cfgdb(&cmd, "away/rmset").await?;

        client
            .cfgdb(&cmd, &format!("file/rmset/re_pair_{tsn}"))
            .await
    }

    #[instrument(skip_all, name = "do_load")]
    async fn do_load(&self, line: &str, data: &MapSv, client: &Client) -> Resp {
        let tech = data.ck_str("tech")?;

        let cmd = json!({
            "line": line,
            "k": tech,
        });

        let body = client.cfgdb(&cmd, "away/listpsn").await?;
        let body = body.ck_array("data")?;

        ok!(body)
    }

    fn ck_data(&self, data: &MapSv) -> RNull {
        data.ck_f64("india")?;
        data.ck_f64("inwidth")?;
        data.ck_f64("outdia")?;
        data.ck_f64("outwidth")?;
        data.ck_f64("angle")?;
        data.ck_f64("standout")?;
        data.ck_f64("kia")?;

        Ok(())
    }

    #[instrument(skip_all, name = "do_move")]
    async fn do_move(&self, line: &str, data: &MapSv, client: &Client, lock: Lock) -> Resp {
        let _ = lock.lock().await;

        let psn = data.ck_str("k")?;
        let box_away = data.ck_str("box")?;
        let pos = match data.k_str("pos") {
            Some(v) => v,
            None => "",
        };
        let ck_tech = data.k_str_empty_trim("tech");

        let cmd = json!({
            "line": line,
            "k": psn,
        });

        let psn_body = match client.cfgdb(&cmd, "psn/get").await {
            Ok(v) => v,
            Err(_) => {
                let cmd = json!({
                    "line": line,
                    "k": "nodata",
                    "v": [{"sn": psn}]
                });

                client.cfgdb(&cmd, "psn/add").await?;

                return ok!(json!("nodata"));
            }
        };
        let psn_body = psn_body.ck_obj("data")?;

        if let Err(_) = self.ck_data(psn_body) {
            return ok!(json!("noall"));
        }

        let task_sn = psn_body.ck_str("task")?;
        let tech_sn = match psn_body.k_str("tech") {
            Some(v) => {
                if let Some(ck) = ck_tech {
                    if ck != v {
                        return ok!(json!("notech"));
                    }
                }

                v
            }
            None => "notech",
        };

        warn!("move step: move psn to away");

        let cmd = json!({
            "line": line,
            "k": tech_sn,
        });

        if let Err(_) = client.cfgdb(&cmd, "away/get").await {
            warn!("addpsn: create new away");

            let cmd = json!({
                "line": line,
                "v": {
                    "sn": tech_sn,
                }
            });
            client.cfgdb(&cmd, "away/add").await?;
        }

        let cmd = json!({
            "line": line,
            "k": tech_sn,
            "v": [psn]
        });

        let _ = client.cfgdb(&cmd, "away/movepsn").await?;

        warn!("move step: away form pack box");

        let cmd = json!({
            "line": line,
            "sn": psn,
        });

        if let Some(box_pack) = psn_body.k_str("boxpack") {
            let _ = client
                .cfgdb(&cmd, &format!("file/rm/box_{box_pack}"))
                .await?;
        }

        warn!("move step: away form pair box");

        if let Some(box_pair) = psn_body.k_str("boxpair") {
            let _ = client
                .cfgdb(&cmd, &format!("file/rm/box_{box_pair}"))
                .await?;
        }

        warn!("move step: update psn");

        let cmd = json!({
            "line": line,
            "k": psn,
            "v": {
                "time": self.cur_time("%Y-%m-%d %H:%M:%S"),
                "task": tech_sn,
                "boxpack": "",
                "boxpair": "",
                "boxaway": box_away,
                "org": "away",
            }
        });

        let _ = client.cfgdb(&cmd, "psn/md").await?;

        warn!("move step: put psn to away box");

        let cmd = json!({
            "line": line,
            "sn": psn,
            "v": {
                "sn": psn,
                "pos": pos,
            }
        });

        let _ = client
            .cfgdb(&cmd, &format!("file/md/box_{box_away}"))
            .await?;

        warn!("move step: remove psn form task");

        let cmd = json!({
            "line": line,
            "k": task_sn,
            "v": [psn],
            "rmpsn": false,
        });

        if let Err(e) = client.cfgdb(&cmd, "task/rmpsn").await {
            warn!("move step: remove psn error: {:?}", e);
        }

        let _ = try_rm_task(line, task_sn, client).await;

        ok!(json!({
            "tech": tech_sn
        }))
    }
}
